//
// Created by denglibin on 19-5-7.
//
#include "settings.h"
#include "shader.h"
#include "buffer_obj.h"
#include "init.h"
#include "call_back.h"
#include "input.h"
#include <GLFW/glfw3.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

/**
 * 创建缓冲对象
 * @param vertices
 * @param indices
 */
static void createBufferObj(float vertices[], int vSize, int indices[], int iSize){
    createVBO(vertices, vSize); //顶点缓冲对象
    createEBO(indices, iSize); //索引缓冲对象
}

/**
 * 设置着色器程序变量的值
 * @param shaderProgramId 着色器程序id
 */
static void setUniformValue(int shaderProgramId){
    float timeValue = glfwGetTime(); //获取运行的秒数
    float greenValue = (sin(timeValue) / 2.0f) + 0.5f; //按正弦曲线变化(周期函数 有循环效果)
    float redValue = (sin(timeValue)) + 0.5f; //按正弦曲线变化(周期函数 有循环效果)
    set4Float(shaderProgramId, "ourColor", redValue, greenValue, 0.0f, 1.0f);
}

static void show(){
    //顶点数据----------------------------------------------------------------
    float vertices[] = {
            0.5f, 0.5f, 0.0f,   // 右上角
            1.0f, 0.0f, 0.0f,   //颜色

            0.5f, -0.5f, 0.0f,  // 右下角
            0.0f, 1.0f, 0.0f,   //颜色

            -0.5f, -0.5f, 0.0f, // 左下角
            0.0f, 0.0f, 1.0f,   //颜色

            -0.5f, 0.5f, 0.0f,   // 左上角
            1.0f, 0.0f, 1.0f    //颜色
    };
    //索引数据
    unsigned int indices[] = { // 注意索引从0开始!
            0, 1, 3, // 第一个三角形
            1, 2, 3  // 第二个三角形
    };
    //初始化
    init();
    //创建窗口对象
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, TITLE, NULL, NULL);
    if (window == NULL)
    {
        printf("Failed to create GLFW window");
        glfwTerminate();
        exit(-1);
    }
    //通知GLFW将窗口的上下文设置为当前线程的主上下文
    glfwMakeContextCurrent(window);
    //对窗口注册一个回调函数,每当窗口改变大小，GLFW会调用这个函数并填充相应的参数供你处理
    glfwSetFramebufferSizeCallback(window, frameSizeChangeCallBack);

    //初始化GLAD用来管理OpenGL的函数指针
    //GLFW给我们的是glfwGetProcAdress，它根据我们编译的系统定义了正确的函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        printf("Failed to initialize GLAD");
        exit(-1);
    }
    //在开始渲染之前必须告诉OpenGL渲染窗口(Viewport)的尺寸大小，这样OpenGL才能知道怎样根据窗口大小显示数据和坐标。
    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);

    //创建着色器程序
    ShaderProgram shaderProgram = {0, "shader_code/vertex01.vs", "shader_code/fragment01.fs"};
    createShaderProgram(&shaderProgram);
    //创建顶点数组对象
    unsigned int VAO; //顶点属性数组对象id
    glGenVertexArrays(1, &VAO); //创建顶点数组对象
    //绑定VAO
    //顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定，任何随后的顶点属性调用都会储存在这个VAO中
    glBindVertexArray(VAO);
    //创建缓冲顶点缓冲对象和索引对象
    createBufferObj(vertices, sizeof(vertices), indices, sizeof(indices));


    /**
    * 设置顶点属性指针(位置):
       第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗？它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中，所以这里我们传入0。
       第二个参数指定顶点属性的大小。顶点属性是一个vec3，它由3个值组成，所以大小是3。
       第三个参数指定数据的类型，这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
       第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE，所有数据都会被映射到0（对于有符号型signed数据是-1）到1之间。我们把它设置为GL_FALSE。
       第五个参数叫做步长(Stride)，它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3(6)个float之后，我们把步长设置为3(6) * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的（在两个顶点属性之间没有空隙）我们也可以设置为0来让OpenGL决定具体步长是多少（只有当数值是紧密排列时才可用）。一旦我们有更多的顶点属性，我们就必须更小心地定义每个顶点属性之间的间隔，我们在后面会看到更多的例子（译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节）。
       最后一个参数的类型是void*，所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头，所以这里是0。
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    //启用顶点属性
    glEnableVertexAttribArray(0);

    /**
     * 设置顶点属性指针(颜色):
     */
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
    //启用顶点属性
    glEnableVertexAttribArray(1);

    // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    glBindVertexArray(0);

    /**
     * 设置图形模式:
     */
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //线框模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); //填充模式
    //渲染循环 使得GLFW在退出之前一直保持运行
    while(!glfwWindowShouldClose(window))
    {
        // 输入控制
        processInput(window);
        //渲染指令
        //当调用glClear函数，清除颜色缓冲之后，整个颜色缓冲都会被填充为glClearColor里所设置的颜色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shaderProgram.id);
        //设置着色器代码中变量的值
        setUniformValue(shaderProgram.id);
        glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized

        //要想绘制我们想要的物体，OpenGL给我们提供了glDrawArrays函数，它使用当前激活的着色器，之前定义的顶点属性配置，和VBO的顶点数据（通过VAO间接绑定）来绘制图元
/**三角形
 * glDrawArrays函数
 * 第一个参数是我们打算绘制的OpenGL图元的类型。由于我们在一开始时说过，我们希望绘制的是一个三角形，这里传递GL_TRIANGLES给它。
 * 第二个参数指定了顶点数组的起始索引，我们这里填0。
 * 最后一个参数指定我们打算绘制多少个顶点，这里是3（我们只从我们的数据中渲染一个三角形，它只有3个顶点长）。
 */
       //  glDrawArrays(GL_TRIANGLES, 0, 3);

/** 矩形(两个三角形)
第一个参数指定了我们绘制的模式，这个和glDrawArrays的一样。
第二个参数是我们打算绘制顶点的个数，这里填6，也就是说我们一共需要绘制6个顶点。
第三个参数是索引的类型，这里是GL_UNSIGNED_INT。
最后一个参数里我们可以指定EBO中的偏移量（或者传递一个索引数组，但是这是当你不在使用索引缓冲对象的时候），但是我们会在这里填写0。
glDrawElements函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER目标的EBO中获取索引。这意味着我们必须在每次要用索引渲染一个物体时绑定相应的EBO，这还是有点麻烦。
不过顶点数组对象同样可以保存索引缓冲对象的绑定状态。VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象。绑定VAO的同时也会自动绑定EBO。
         */
      glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        //检查并调用事件，交换缓冲
        glfwSwapBuffers(window); //交换颜色缓冲
        //检查触发事件 函数检查是否有触发事件，比如键盘、鼠标等信号输入，然后更新窗口状态，调用相应的回调函数（可通过回调方法手动设置）。
        glfwPollEvents();
    }
    //释放/删除之前的分配的所有资源
    glfwTerminate();
}

int main1(){
    show();
    return 0;
}
